#include <assert.h>
#include <math.h>
#include <pthread.h>
#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>
#include <unistd.h>

#include "base/simapi.h"
#include "util/ticket.h"

#include "config.h"
#ifdef WITH_TM
# include "tm/tmapi.h"
#endif

int numVirtualProcessorsLocal = 1;
bool tcc;

/** 
    Approximating behavior of JikesRVM timeSlicerThreadMain
*/
void* timeSlicerThreadMain (void *arg)
{
    Sim_Print_warning(false);
    Sim_Print2("Starting timeSlicerThreadMain (processor %d of %d)\n", Sim_GetMyId(), Sim_GetNumCpus());
    Sim_SetIgnoreProgress(true);
    for (;;) {
        ;
    }
    return NULL;
}
/** 
    Approximating behavior of JikesRVM setTimeSlicer
*/
void setTimeSlicer (int msTimerDelay)
{
    pthread_t timeSlicerThread; // timeSlicerThread is a write-only dummy
                                // variable.
    int nsTimerDelay = msTimerDelay * 1000 * 1000;
    int errorCode = pthread_create(&timeSlicerThread, NULL,
                                   timeSlicerThreadMain, (void*)nsTimerDelay);
    assert(errorCode == 0);
}

/** 
    Approximating behavior of JikesRVM sysVirtualProcessorEnableTimeSlicing
*/
void sysVirtualProcessorEnableTimeSlicing (int timeSlice)
{
    setTimeSlicer(timeSlice);
}

/** 
    Approximating behavior of JikesRVM sysVirtualProcessorStartup
*/
void sysVirtualProcessorStartup ()
{
    Sim_Print_warning(false);
    Sim_Print2("Starting sysVirtualProcessorStartup (processor %d of %d)\n", Sim_GetMyId(), Sim_GetNumCpus());
    // jw :all created threads first have to get a ticket to run for fast forwarding
    Ticket_Wait();
    goto_real();
}

/** 
    Approximating behavior of JikesRVM sysWaitForVirtualProcessorInitialization
*/
void sysWaitForVirtualProcessorInitialization ()
{
    // jw : now this thread finished the first phase of thread booting.
    //      pass the ticket to the next thread
    goto_sim();
    Ticket_Yield(numVirtualProcessorsLocal);
    goto_real();
}

/** 
    Approximating behavior of JikesRVM sysWaitForMultithreadingStart
*/
void sysWaitForMultithreadingStart ()
{
    // jw : the second part of the thread booting sequence is done.
    //      pass the ticket to the next thread
    goto_sim();
    Ticket_Yield(numVirtualProcessorsLocal);
    if (Sim_GetMyId() == 0) {
        // Only the first thread(i.e. the main pthread) calls this part.
        // From now on any thread can run, no more round robin scheduling.

        Ticket_EnableSharedTicket();

        goto_real();

        // the main thread would perform a goto_sim() here, but
        // since there is no real main thread we absorb that op
        for (int i=0; i < 1000; i++) {
            ;
        }
        goto_sim();
    }
}

/** 
    Approximating behavior of JikesRVM sysNanosleep
*/
void sysNanosleep (long long howLongNanos) {
}

/** 
    Approximating behavior of application threads including startup in VM_IdleThread.run
*/
void* run (void* arg) {
    sysVirtualProcessorStartup();
    sysWaitForVirtualProcessorInitialization();
    sysWaitForMultithreadingStart();
    Sim_Print_warning(true);
    Sim_Print2("Starting program (processor %d of %d)\n", Sim_GetMyId(), Sim_GetNumCpus());
    Sim_BeginStats();
    goto_real();
    goto_sim();
    while (true) {
        // From VM_IdleThread.run
        sysNanosleep(1000 * 1000);

        // do some work
        for (int i=0; i < 1000; i++) {
            if (tcc) {
#ifdef WITH_TM
                TM_BeginClosed();
                TM_EndClosed();
#else
                Sim_Print0("Somehow mode is MODE_TM but WITH_TM was not defined.");
                exit(1);
#endif
            }
        }
    }

    Sim_EndStats();
    return NULL;
}

/** 
    Approximating behavior of JikesRVM fast forwarded multi-threaded startup
*/
void mainX (int argc, const char **argv, const char **envp) {
    tcc = Sim_GetMode() == MODE_TM;

    // -1 for timeSlicerThreadMain
    int thread_count = Sim_GetNumCpus()-1;
    if (thread_count == 0) {
        printf("Exiting because thread_count == 0\n");
        _exit(0);
    }

    /* 
       Based on JikesRVM mainX
    */
    Sim_SetPrintInstructions(false);
    Sim_Print_warning(false);
    Sim_Print2("Starting mainX (processor %d of %d)\n", Sim_GetMyId(), Sim_GetNumCpus());
    goto_real();
    Sim_DisableShutdownAsserts();

    /*
      Based on VM_Scheduler.boot and sysVirtualProcessorCreate
    */
    printf("Starting %d threads...\n", thread_count);
    pthread_t* threads = (pthread_t*)calloc(thread_count, sizeof(pthread_t));
    assert(threads != NULL);
    for (int i = 1 ; i < thread_count; i++) {
        int createResult = pthread_create(&threads[i], 
                                          NULL,
                                          run,
                                          NULL);
        assert(createResult == 0);
        numVirtualProcessorsLocal++;
    }
    sysWaitForVirtualProcessorInitialization();
    sysVirtualProcessorEnableTimeSlicing(-1);
    sysWaitForMultithreadingStart();

    /* 
       we "thread switch" here to main thread...
    */
    Sim_Print2("Starting \"MainThread\" (processor %d of %d)\n", Sim_GetMyId(), Sim_GetNumCpus());

    for (int i=0; i < 100; i++) {
        for (int j=0; j < 100; j++) {
            if (tcc) {
#ifdef WITH_TM
                TM_BeginClosed();
                TM_EndClosed();
#endif
            }
        }
        Sim_Print1("%d\n", i);
    }
    Sim_Print0("syscall time...\n");
    printf("Trying to _exit (plain exit hangs)...\n");
    _exit(0);
}
